# coding=utf-8
from django.db.models import Q, Sum, Subquery, OuterRef
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import status, views
from rest_framework.decorators import action
from rest_framework.response import Response
from MSB_ERP.utils.cont import NumberPrefix
from MSB_ERP.utils.generate_code import generate_code
from rest_framework import generics
from MSB_ERP.utils.pagination import GlobalPagination
from MSB_ERP.utils.base_serializer import ChoiceGoodsSerializer
from goods_info.models import GoodsInventoryModel, GoodsCategoryModel, GoodsModel
from erp_system.models import UserModel


class MultipleDestoryMixin:
    '''
    定义批量删除的视图函数
    '''
    del_ids = openapi.Schema(type=openapi.TYPE_OBJECT, required=['ids'], properties={
        # 参数名：接一个类型（类型一个是openapi.Schema开头的）
        # 因为他是整数，所以要openapi.TYPE_INTEGER整数类型
        'ids': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_INTEGER),
                              description='选择那些需要删除（主键）的列表')
    })

    # 这个是特别的接口文档，methods表示请求的方法，request_body表示指定传参
    @swagger_auto_schema(method='delete', request_body=del_ids, operation_description='批量删除')  # 装饰器一定要写在action的上面
    @action(methods=['delete'], detail=False)
    def multiple_delete(self, request, *args, **kwargs):
        delete_ids = request.data.get('ids')
        # 如果没有数据
        if not delete_ids:
            return Response(
                data={'detail': '参数错误，ids是必传参数'},
                status=status.HTTP_400_BAD_REQUEST
            )
        # 传过来的数据必须是列表
        if not isinstance(delete_ids, list):
            return Response(
                data={'detail': '参数错误，ids是必须是一个列表'},
                status=status.HTTP_400_BAD_REQUEST
            )
        # 其实这里是调用了子类的query
        queryset = self.get_queryset()

        # 得到删除的query
        del_queryset = queryset.filter(id__in=delete_ids)

        # 保护性代码：看传进来的数据是否长度是否一样
        ''' .count()才是计算查询结果集中的行数，.count是获取函数对象 '''
        if del_queryset.count() != len(delete_ids):
            return Response(
                data={'detail': '删除的数据不存在'},
                status=status.HTTP_400_BAD_REQUEST
            )
        # 真正的做删除操作
        del_queryset.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


class MultipleOpenMixin:
    '''
    定义批量启用或者禁用的视图函数
    '''
    # 接口文档
    del_ids = openapi.Schema(type=openapi.TYPE_OBJECT, required=['ids'], properties={
        # 参数名：接一个类型（类型一个是openapi.Schema开头的）
        # 因为他是整数，所以要openapi.TYPE_INTEGER整数类型
        'ids': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_INTEGER),
                              description='选择那些需要批量的ID（主键）的列表'),
        'is_open': openapi.Schema(type=openapi.TYPE_STRING, description='是否需要启用，启用：0，禁止：1')
    })

    # 这个是特别的接口文档，methods表示请求的方法，request_body表示指定传参
    @swagger_auto_schema(method='delete', request_body=del_ids, operation_description='批量启用或者禁用')  # 装饰器一定要写在action的上面
    @action(methods=['delete'], detail=False)
    def multiple_open(self, request, *args, **kwargs):
        delete_ids = request.data.get('ids')
        is_open = request.data.get('is_open', 0)
        # 如果没有数据
        if not delete_ids:
            return Response(
                data={'detail': '参数错误，ids是必传参数'},
                status=status.HTTP_400_BAD_REQUEST
            )
        # 传过来的数据必须是列表
        if not isinstance(delete_ids, list):
            return Response(
                data={'detail': '参数错误，ids是必须是一个列表'},
                status=status.HTTP_400_BAD_REQUEST
            )
        # 其实这里是调用了子类的query
        queryset = self.get_queryset()

        # 得到删除的query
        del_queryset = queryset.filter(id__in=delete_ids)

        # 保护性代码：看传进来的数据是否长度是否一样
        ''' .count()才是计算查询结果集中的行数，.count是获取函数对象 '''
        if del_queryset.count() != len(delete_ids):
            return Response(
                data={'detail': '数据不存在'},
                status=status.HTTP_400_BAD_REQUEST
            )
        # 真正的修改数据
        del_queryset.update(delete_flag=is_open)
        return Response(status=status.HTTP_200_OK)


# 生成各种编号的接口
class GenerateCode(views.APIView):
    # openapi.Parameter用于描述请求中的单个参数，可以应用于方法参数上。
    # name表示传参的名字（url上的名字）
    # in_从哪里传参,IN_QUERY从请求路径中传参
    # type最后定义类型（openapi.TYPE_STRING字符串类型）
    prefix_param = openapi.Parameter(name='prefix', in_=openapi.IN_QUERY, description='编号的前缀，可以参考cont.py的文件',
                                     type=openapi.TYPE_STRING)

    @swagger_auto_schema(manual_parameters=[prefix_param], operation_description='自动生成各种编号（流水号）')  # 装饰器一定要写在action的上面
    def get(self, reqest, *args, **kwargs):
        '''
        生成各种编号的接口，必须传一个前缀：/api/generate_code/prefix=ord  参考cont.py
        返回一个28位的编号字符串，return：code就是生成的编号
        '''
        prefix = reqest.query_params.get('prefix', None)
        if prefix:  # 如果传了参数的话
            # __members__表示枚举类的成员也就是前面的键（也就是键）
            if prefix in NumberPrefix.__members__:  # 表示传进来的参数在枚举类的成员里面
                # 因为上面传过来的是键，所以要根据键去取值（用字典的方式取值就行）
                code = generate_code(NumberPrefix[prefix].value)
                return Response(data={'code': code}, status=status.HTTP_200_OK)
            else:
                # detail错误提示
                return Response(data={'detail': 'prefix没有配置，参考cont.py'}, status=status.HTTP_400_BAD_REQUEST)
        else:
            return Response(data={'detail': 'prefix没有，该参数是必须传的'}, status=status.HTTP_400_BAD_REQUEST)


class ChoiceGoodsSearchView(generics.GenericAPIView):
    serializer_class = ChoiceGoodsSerializer
    pagination_class = GlobalPagination

    # 分页参数必须是query_param(看源码)
    page_param = openapi.Parameter(name='page', in_=openapi.IN_QUERY, description="页号", type=openapi.TYPE_INTEGER)
    size_param = openapi.Parameter(name='size', in_=openapi.IN_QUERY, description="每页显示数量", type=openapi.TYPE_INTEGER)

    # ChoiceGoodsSerializer里面的label就是参数说明
    @swagger_auto_schema(operation_description='搜索需要选择的货品列表', request_body=ChoiceGoodsSerializer,
                         # 定义的是ChoiceGoodsSerializer（serializer）
                         manual_parameters=[page_param, size_param])
    def post(self, request, *args, **kwargs):
        # 用序列化器进行接收（他包含序列化和反序列化）
        # 请求中获取参数
        ser = ChoiceGoodsSerializer(data=request.data)  # 反序列化接收参数用data
        if ser.is_valid(): # 验证过了
            # 序列化已经接收好参数了，要从序列化中拿取数据
            # 这里验证好的数据会放在validated_data里面，他里面是字典格式用get取值
            keyword = ser.validated_data.get('keyword', None)
            warechouse_id = ser.validated_data.get('warechouse_id', 0)
            category_id = ser.validated_data.get('category_id', 0)
            number_code = ser.validated_data.get('number_code', None)

            query = Q()
            # 根据仓库来过滤查询，并且查询每个商品的库存
            '''把商品库存查询结果作为子查询'''
            if warechouse_id:  # 有指定仓库
                inventory_list = GoodsInventoryModel.objects.filter(warehouse_id=warechouse_id).filter(
                    goods_id=OuterRef('id')).annotate(total_sum=Sum('cur_inventory'))

            else:  # 没有指定仓库，意味着：查询每个商品的总库存和
                '''把商品库存查询结果作为子查询'''
                # 根据goods_id来进行分组查询，查询库存表里面的当前库存的总和
                # （结果是字典加列表的嵌套） --> [{'goods':'cur_inventory'},...]
                # 分组的话就要用到annotate和进行求和操作，Sum根据哪个字段进行求和
                ''' OuterRef基于哪一个表进行连表查询（当前这个结果里面的商品ID）和后面result结果进行连表'''
                # OuterRef连表查询条件，和外表进行查询不是和自己连表
                inventory_list = GoodsInventoryModel.objects.filter(goods_id=OuterRef('id')).values(
                    'goods_id').annotate(total_sum=Sum('cur_inventory'))

            # 基于GoodsModel的过滤查询
            if keyword:
                child_query = Q()
                child_query.add(Q(name__contains=keyword), 'OR')
                child_query.add(Q(specification=keyword), 'OR')
                child_query.add(Q(model_number=keyword), 'OR')
                query.add(child_query, 'AND')
            if category_id:
                query.add(Q(category__id=category_id), 'AND')
            if number_code:
                query.add(Q(number_code__contains=number_code), 'AND')

            '''
            Django的优化：
            数据库里面一个表会有几百或者更多字段，在前端最终只需要8个或者10个，我们最好不要返回整个模型（这样的话相当于整个模型都返回了）
            相当于scect *一样，我们就指定返回字段多一个都不要，节省了内存空间，减少了数据库执行sql语句的时间
            '''

            # 这里是除了库存信息，都查出来了，接下来要分组查询（annotate根据商品ID进行分组）
            result = GoodsModel.objects.filter(query).values('id', 'name', 'number_code', 'specification',
                                                             'model_number', 'color', 'units__basic_name',
                                                             'category__name', 'category_id') \
                .annotate(cur_inventory=Subquery(inventory_list.values('total_sum'))).all()  # Subquery子查询（基于子查询的连表查询）

            # 分页
            pg_result = self.paginate_queryset(result)
            # pg_result就是查询结果，借助序列化器帮忙序列化
            result_ser = self.get_serializer(instance=pg_result, many=True)

        return self.get_paginated_response(data=result_ser.data)

class MultipleAuditMixin:
    '''
    定义批量审核的或者反审核
    '''
    body_json = openapi.Schema(type=openapi.TYPE_OBJECT, required=['ids','user_id'], properties={
        # 参数名：接一个类型（类型一个是openapi.Schema开头的）
        # 因为他是整数，所以要openapi.TYPE_INTEGER整数类型
        'ids': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_INTEGER),
                              description='选择那些需要删除（主键）的列表'),
        'user_id': openapi.Schema(type=openapi.TYPE_INTEGER, description='审核用户的ID'),
        'is_audit': openapi.Schema(type=openapi.TYPE_STRING, description='是否审核，审核：1，反审核：0')
    })

    # 这个是特别的接口文档，methods表示请求的方法，request_body表示指定传参
    @swagger_auto_schema(method='put', request_body=body_json, operation_description='审核反审核')  # 装饰器一定要写在action的上面
    @action(methods=['put'], detail=False)
    def multiple_audit(self, request, *args, **kwargs):
        audit_ids = request.data.get('ids')
        user_id = request.data.get('user_id') # 用户ID
        is_audit = request.data.get('is_audit','1') # 是否审核

        # 如果没有数据
        if not audit_ids:
            return Response(
                data={'detail': '参数错误，ids是必传参数'},
                status=status.HTTP_400_BAD_REQUEST
            )
        # 传过来的数据必须是列表
        if not isinstance(audit_ids, list):
            return Response(
                data={'detail': '参数错误，ids是必须是一个列表'},
                status=status.HTTP_400_BAD_REQUEST
            )
        # 其实这里是调用了子类的query
        queryset = self.get_queryset()

        # 得到删除的query
        del_queryset = queryset.filter(id__in=audit_ids)

        # 保护性代码：看传进来的数据是否长度是否一样
        ''' .count()才是计算查询结果集中的行数，.count是获取函数对象 '''
        if del_queryset.count() != len(audit_ids):
            return Response(
                data={'detail': '审核操作的数据不存在'},
                status=status.HTTP_400_BAD_REQUEST
            )

        for item in del_queryset.all(): # 加all可以变成列表
            # item代表一个订单
            if item.status != '1' and is_audit == '0': # 订单状态为1，是不可以反审核的
                return Response(
                data={'detail': '不能反审核，因为订单已经生效'},
                status=status.HTTP_400_BAD_REQUEST
            )
            if item.status != '0' and is_audit == '1': # 订单状态不是0，则是不可以审核
                return Response(
                data={'detail': '不能审核，因为订单已经生效'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # 用户ID
        if not user_id:
            return Response(
                data={'detail': '参数错误，user_id是必传的'},
                status=status.HTTP_400_BAD_REQUEST
            )
        check_user = UserModel.objects.get(id=int(user_id))

        # 真正的审核和反审核

        # 真正的做删除操作
        del_queryset.update(status=is_audit,check_user_name=check_user.real_name,check_user_id=check_user.id)

        return Response(status=status.HTTP_200_OK)



